package bdsender

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/project-flogo/core/data/coerce"
	"github.com/project-flogo/core/data/metadata"

	"github.com/project-flogo/core/activity"

	// initialize script engine
	_ "github.com/project-flogo/core/data/expression/script"
)

// Activity activity object
type Activity struct {
	verify bool
	slots  []Slot
}

func init() {
	_ = activity.Register(&Activity{}, New)
}

// New create new activity
func New(ctx activity.InitContext) (activity.Activity, error) {
	var settings Settings
	err := metadata.MapToStruct(ctx.Settings(), &settings, true)
	if err != nil {
		return nil, err
	}
	slots := make([]Slot, len(settings.Slots))
	for idx, params := range settings.Slots {
		slot, err := parseSlot(params)
		if err != nil {
			return nil, err
		}
		slots[idx] = slot
	}
	ctx.Logger().Debugf("slots: %+v", slots)
	act := &Activity{
		slots: slots,
	}
	return act, nil
}

var activityMd = activity.ToMetadata(&Input{}, &Output{}, &Settings{})

// Metadata activity metadata
func (a *Activity) Metadata() *activity.Metadata {
	return activityMd
}

// Eval activity evaluation function
func (a *Activity) Eval(ctx activity.Context) (bool, error) {
	input := Input{}
	err := ctx.GetInputObject(&input)
	if err != nil {
		return true, err
	}
	if input.SlotSelection < 0 || input.SlotSelection >= len(a.slots) {
		return true, fmt.Errorf("invalid slot selection %d", input.SlotSelection)
	}
	slot := a.slots[input.SlotSelection]
	data, err := json.Marshal(input.Data)
	if err != nil {
		return true, err
	}
	value, err := slot.Encode(string(data))
	if err != nil {
		return true, err
	}
	output := Output{
		Data: value,
	}
	err = ctx.SetOutputObject(&output)
	if err != nil {
		return true, err
	}

	return true, nil
}

func parseSlot(params interface{}) (Slot, error) {
	switch m := params.(type) {
	case map[string]interface{}:
		{
			return parseSlotMap(m)
		}
	case string:
		{
			ms := map[string]interface{}{}
			err := json.Unmarshal([]byte(m), &ms)
			if err != nil {
				return Slot{}, err
			}
			return parseSlot(ms)
		}
	default:
		{
			v, err := json.Marshal(params)
			if err != nil {
				return Slot{}, err
			}
			ms := map[string]interface{}{}
			err = json.Unmarshal(v, &ms)
			if err != nil {
				return Slot{}, err
			}
			return parseSlotMap(ms)
		}
	}
}

func parseSlotMap(m map[string]interface{}) (Slot, error) {
	slot := Slot{}
	if val, ok := m["head"]; ok {
		str, err := coerce.ToString(val)
		if err != nil {
			return slot, err
		}
		head, err := parseBytes(str)
		if err != nil {
			return slot, err
		}
		slot.Head = head
	}
	if val, ok := m["items"]; ok {
		items, err := parseItems(val)
		if err != nil {
			return slot, err
		}
		slot.Items = items
	}
	return slot, nil
}

func parseBytes(s string) ([]byte, error) {
	arr := strings.Split(s, ",")
	if len(arr) == 0 {
		return []byte{}, nil
	}
	bs := make([]byte, len(arr))
	for idx, h := range arr {
		b, err := parseByte(h)
		if err != nil {
			return []byte{}, err
		}
		bs[idx] = b
	}
	return bs, nil
}

func parseByte(s string) (byte, error) {
	val, err := strconv.ParseUint(strings.TrimSpace(s), 0, 32)
	if err != nil {
		return 0, err
	}
	if val >= 256 {
		return 0, fmt.Errorf("invalid byte: %s", s)
	}
	return byte(val), nil
}

func parseItems(s interface{}) ([]item, error) {
	items := []item{}
	b, err := json.Marshal(s)
	if err != nil {
		return items, err
	}
	err = json.Unmarshal(b, &items)
	if err != nil {
		return items, err
	}
	return items, nil
}
